package udpserver

import (
	"context"
	"go-net/internal/udpserver/packet"
	"go-net/internal/udpserver/packet/deadline"
	"net"
	"sync"
	"time"
)

type Connection struct {
	listener *listener

	rAddr net.Addr

	buffer *packet.Buffer

	doneCh   chan struct{}
	doneOnce sync.Once

	writeDeadline *deadline.Deadline
}

// Read reads from c into p
func (c *Connection) Read(p []byte) (int, error) {
	return c.buffer.Read(p)
}

// Write writes len(p) bytes from p to the DTLS connection
func (c *Connection) Write(p []byte) (n int, err error) {
	select {
	case <-c.writeDeadline.Done():
		return 0, context.DeadlineExceeded
	default:
	}
	return c.listener.udpConn.WriteTo(p, c.rAddr)
}

// Close closes the conn and releases any Read calls
func (c *Connection) Close() error {
	var err error
	c.doneOnce.Do(func() {
		c.listener.connWG.Done()
		close(c.doneCh)
		c.listener.connLock.Lock()
		delete(c.listener.connections, c.rAddr.String())
		nConns := len(c.listener.connections)
		c.listener.connLock.Unlock()

		if nConns == 0 && !c.listener.accepting.Load().(bool) {
			// Wait if this is the final connection
			c.listener.readWG.Wait()
			if errClose, ok := c.listener.errClose.Load().(error); ok {
				err = errClose
			}
		} else {
			err = nil
		}
	})

	return err
}

// LocalAddr implements net.udpConn.LocalAddr
func (c *Connection) LocalAddr() net.Addr {
	return c.listener.udpConn.LocalAddr()
}

// RemoteAddr implements net.udpConn.RemoteAddr
func (c *Connection) RemoteAddr() net.Addr {
	return c.rAddr
}

// SetDeadline implements net.udpConn.SetDeadline
func (c *Connection) SetDeadline(t time.Time) error {
	c.writeDeadline.Set(t)
	return c.SetReadDeadline(t)
}

// SetReadDeadline implements net.udpConn.SetDeadline
func (c *Connection) SetReadDeadline(t time.Time) error {
	return c.buffer.SetReadDeadline(t)
}

// SetWriteDeadline implements net.udpConn.SetDeadline
func (c *Connection) SetWriteDeadline(t time.Time) error {
	c.writeDeadline.Set(t)
	// Write deadline of underlying connection should not be changed
	// since the connection can be shared.
	return nil
}
